#ifndef __ZTOUCHPROTO_H
#define __ZTOUCHPROTO_H

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>//sockaddr_in
#include <fcntl.h>
#include <cstdlib>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
#include <string>
#include <list>
#include <mutex>
#include <sys/time.h>

#define _DBG

namespace ZTouch
{
	
using namespace std;

typedef int socket_t;

struct ZTouchDataHead
{
	private:int length;//后接消息的长度
	public:enum Type{UNDEFINE, LOGIN, LOGINFAIL, LOGINSUCCEED, LOGOUT, ASK, KEEP, DESNOTFOUND, CHATING, SENDSUCCEED, SENDFAIL, TERMI};
	private:Type type;
	
	public:ZTouchDataHead(int _length, Type _type):length(htonl(_length)), type(static_cast<enum Type>(htonl(_type))) {}
	public:ZTouchDataHead(void):length(htonl(0)), type(static_cast<enum Type>(htonl(Type::UNDEFINE))) {}
	public:ZTouchDataHead(const ZTouchDataHead& another):length(another.length),type(another.type) {}
	
	public:void setLength(int _len){length = htonl(_len);}
	public:void setType(Type _type){type = static_cast<enum Type>(htonl(_type));}
	public:int getLength(void) const {return ntohl(length);}
	public:Type getType(void) const {return static_cast<enum Type>(ntohl(type));}
	
	public:void clean(void)
	{
		length = 0;
		type = Type::UNDEFINE;
	}
};

struct ZTouchMsg
{
	public:enum ContentType{TEXT, BINARY};
		
	private:int srcNameLength;
	private:int desNameLength;
	private:ContentType type;
	private:int contentLength;
	public:char content[1];/*src, des, content*/
	
	public:void setSrcNameLength(int len) {srcNameLength = htonl(len);}
	public:int getSrcNameLength(void) const {return ntohl(srcNameLength);}
	public:void setDesNameLength(int len) {desNameLength = htonl(len);}
	public:int getDesNameLength(void) const {return ntohl(desNameLength);}
	public:void setContentLength(int len) {contentLength = htonl(len);}
	public:int getContentLength(void) const {return ntohl(contentLength);}
	public:void setType(ContentType _type) {type = static_cast<ContentType>(htonl(_type));}
	public:ContentType getType(void) const {return static_cast<ContentType>(ntohl(type));}
	
};

struct UserInfo
{
	string		id;
	socket_t	connect;

	UserInfo(void){}
	
	UserInfo(string _id, socket_t _connect):id(_id), connect(_connect){}
			
	bool operator == (const UserInfo& another) const
	{return id == another.id;}	
	bool operator != (const UserInfo& another) const
	{return id != another.id;}			
	bool operator >= (const UserInfo& another) const
	{return id >= another.id;}		
	bool operator <= (const UserInfo& another) const
	{return id <= another.id;}		
	bool operator > (const UserInfo& another) const
	{return id > another.id;}	
	bool operator < (const UserInfo& another) const
	{return id < another.id;}
	
	UserInfo(const UserInfo& another):id(another.id.c_str()), connect(another.connect){}
	
	UserInfo& operator = (const UserInfo& another)
	{
		id = string(another.id.c_str());
		connect = another.connect;
		return *this;
	}
			
	friend ostream& operator << (ostream& ost, const UserInfo& info)
	{
		ost<<"id:"<<info.id<<" | "<<"socket:"<<info.connect<<endl;
		return ost;
	}	
};

class ZMessage
{
	private:ZTouchDataHead		*	head;
	
	private:char 				*	buffer;
	private:ZTouchMsg			*	msg;
	
	private:char				*	srcName;
	private:char				*	desName;
	private:char				*	msgContent;
	
	private:bool					ready;
	
	public:ZMessage(void):
	head(nullptr), buffer(nullptr), msg(nullptr), 
	srcName(nullptr), desName(nullptr), msgContent(nullptr),
	ready(false){}
	
	public:explicit ZMessage(const ZTouchDataHead& headInfo):
	head(nullptr), buffer(nullptr), msg(nullptr), 
	srcName(nullptr), desName(nullptr), msgContent(nullptr),
	ready(false)
	{
		int msgLength = headInfo.getLength();
		if
		(
			(
				headInfo.getType() != ZTouchDataHead::CHATING &&
				headInfo.getType() != ZTouchDataHead::LOGIN
			) ||
			msgLength < static_cast<int>(sizeof(ZTouchMsg))
		)	return;
		
/*buf*/	buffer = (char*) malloc(sizeof(ZTouchDataHead) + msgLength);
/*hed*/	head = (ZTouchDataHead*) buffer; (*head) = headInfo;
/*msg*/	msg = (ZTouchMsg*) (buffer + sizeof(ZTouchDataHead));
	}
	
	private:void unuse(void)
	{
		buffer = nullptr;
		head = nullptr;
		msg = nullptr;
		
		srcName = desName = msgContent = nullptr;
		ready = false;
	}
	
	public:explicit ZMessage(const ZTouchDataHead& headInfo, socket_t connect, int flag = MSG_WAITALL):ZMessage(headInfo)
	{
		int msgLength = headInfo.getLength();
		int recvLength = recv(connect, buffer, ntohl(msgLength), flag);
		
		if(flag != MSG_DONTWAIT && recvLength != msgLength)
		{
			free(buffer);
			unuse();
			return;
		}
		
		srcName = msg->content;
		desName = srcName + msg->getSrcNameLength() + 1;
		msgContent = desName + msg->getDesNameLength() + 1;
		
		ready = true;
	}
	
	private:ZMessage(const ZMessage& another){}
	
	public:ZMessage(ZMessage&& another) noexcept:
	head(another.head), buffer(another.buffer), msg(another.msg), 
	srcName(another.srcName), desName(another.desName), msgContent(another.msgContent),
	ready(another.ready)	{another.unuse();}
	
	private:ZMessage& operator = (const ZMessage& another){return *this;}
	
	public:ZMessage& operator = (ZMessage&& another) noexcept
	{
		if(this == &another) return *this;
		
		buffer = another.buffer;
		head = another.head;
		msg = another.msg;
		ready = another.ready;
		
		srcName = another.srcName;
		desName = another.desName;
		msgContent = another.msgContent;
		
		another.unuse();
		
		return *this;
	}
	
	public:~ZMessage(void) {free(buffer);}
	
	public:bool load(socket_t connect, const ZTouchDataHead& _head, int flag = MSG_WAITALL)
	{
		if(ready)
		{
			free(buffer);
			unuse();
		}
		int msgLength = _head.getLength();
		
		if
		(
			(
				_head.getType() != ZTouchDataHead::CHATING &&
				_head.getType() != ZTouchDataHead::LOGIN
			) ||
			msgLength < (int) sizeof(ZTouchMsg)
		)	
		{
			#ifdef _DBG
				cout<<"return 1"<<endl;
				cout<<"type:"<<_head.getType()<<endl;
				cout<<"msgLength:"<<msgLength<<endl;
				cout<<"sizeof(ZTouchMsg):"<<(int) sizeof(ZTouchMsg)<<endl;
			#endif
			return false;
		}
		
		int recvLength = 0;
/*buf*/	buffer = (char*) malloc(sizeof(ZTouchDataHead) + msgLength);
/*hed*/	head = (ZTouchDataHead*) buffer; (*head) = _head;
/*msg*/	msg = (ZTouchMsg*) (buffer + sizeof(ZTouchDataHead));
		
		recvLength = recv(connect, msg, msgLength, flag);
		
		if(flag != MSG_DONTWAIT && recvLength != msgLength)
		{
			free(buffer);
			unuse();
			#ifdef _DBG
				cout<<"return 2"<<endl;
			#endif
			return false;
		}
		
		srcName = msg->content;
		desName = srcName + msg->getSrcNameLength() + 1;
		msgContent = desName + msg->getDesNameLength() + 1;
		
		ready = true;
		return true;
	}
	
	public:static ZMessage createMessage
	(
		/*ZTouchDataHead&	_head, */
		const string&	_sendUserName,
		const string&	_recvUserName,
		const char*		_message,
		int				_messageSize,/*text's length should be + 1*/
		ZTouchMsg::ContentType _msgType = ZTouchMsg::ContentType::TEXT,
		ZTouchDataHead::Type _type = ZTouchDataHead::CHATING
	)
	{
		ZMessage resultMsg;
		
		/*each +1 for '\0'*/
		int msgLength = 
			sizeof(ZTouchMsg) + 
			_sendUserName.length() + 1 +
			_recvUserName.length() + 1 +
			_messageSize;
		
		//creat buffer
		resultMsg.buffer = (char*) malloc(sizeof(ZTouchDataHead) + msgLength);
		resultMsg.head = (ZTouchDataHead*) resultMsg.buffer;
		resultMsg.msg = (ZTouchMsg*) (resultMsg.buffer + sizeof(ZTouchDataHead));
		//create the head
		resultMsg.head->setLength(msgLength);
		//cout<<">>>>getLength == "<<resultMsg.head.getLength()<<endl;
		resultMsg.head->setType(_type);
			
		resultMsg.msg->setSrcNameLength(_sendUserName.length());
		resultMsg.msg->setDesNameLength(_recvUserName.length());
		resultMsg.msg->setType(_msgType);
		resultMsg.msg->setContentLength(_messageSize);
		
		resultMsg.srcName = resultMsg.msg->content;
		resultMsg.desName = resultMsg.srcName + _sendUserName.length() + 1;
		resultMsg.msgContent = resultMsg.desName + _recvUserName.length() + 1;
		
		memcpy(resultMsg.srcName, _sendUserName.c_str(), _sendUserName.length()+1);
		memcpy(resultMsg.desName, _recvUserName.c_str(), _recvUserName.length()+1);
		memcpy(resultMsg.msgContent, _message, _messageSize);
		
		resultMsg.ready = true;
		return resultMsg;
	}
	
	public:bool sendTo(socket_t connect, int flag = MSG_WAITALL) const
	{
		if(!ready) throw "not ready";
		
		int msgLength = sizeof(ZTouchDataHead) + head->getLength();
		
		int sendLength = send(connect, buffer, msgLength, flag);
		if(flag != MSG_DONTWAIT  &&  sendLength != msgLength) return false;
		
		return true;
	}
	
	public:const ZTouchDataHead& getHead(void)
	{
		if(head == nullptr) throw "not ready";
		return *head;
	}
	
	public:static bool getHead(socket_t connect, ZTouchDataHead& hd, int flag = MSG_WAITALL)
	{
		int recvLength = recv(connect, &hd, sizeof(ZTouchDataHead), flag);
		if(recvLength != sizeof(ZTouchDataHead)) return false;
		return true;
	}
	
	public:static bool putHead(socket_t connect, const ZTouchDataHead& hd, int flag = MSG_WAITALL)
	{
		int sendLength = send(connect, &hd, sizeof(ZTouchDataHead), flag);
		if(sendLength != sizeof(ZTouchDataHead)) return false;
		return true;
	}
	
	public:static bool sendKeep(socket_t connect, int flag = MSG_WAITALL)
	{
		ZTouchDataHead keep(0, ZTouchDataHead::KEEP);
		
		//cout<<"len:"<<keep.getLength()<<"  typ:"<<keep.getType()<<endl;
		
		return putHead(connect, keep, flag);
	}
	
	public:string getSenderName(void) const
	{
		if(!ready) throw "not ready";		
		return string(srcName);
	}
	
	public:string getReciverName(void) const
	{
		if(!ready) throw "not ready";
		return string(desName);
	}
	
	public:int getContentLength(void) const
	{
		if(!ready) throw "not ready";
		return msg->getContentLength();
	}
	
	public:ZTouchMsg::ContentType getContentType(void) const
	{
		if(!ready) throw "not ready";
		return msg->getType();
	}
	
	public:static ZTouchDataHead::Type requestLogin(socket_t connect, const string& userName)
	{
		ZTouchDataHead loginAck(userName.length() + 1, ZTouchDataHead::LOGIN);
		if(ZMessage::putHead(connect, loginAck, MSG_WAITALL))
		{
			int sentLength = send(connect, userName.c_str(), userName.length() + 1, MSG_WAITALL);
			if(static_cast<long>(sentLength) == static_cast<long>(userName.length() + 1)  &&  ZMessage::getHead(connect, loginAck, MSG_WAITALL))
			{
				return loginAck.getType();
			}
		}
		return ZTouchDataHead::LOGINFAIL;
	}
	
	public:const char* getSrcName(void) const {return srcName;}
	public:const char* getDesName(void) const {return desName;}
	public:const char* getContent(void) const {return msgContent;}
	
	public:int getMsgLength(void) const {if(head != nullptr) return head->getLength(); else return -1;}
	
	public:bool isReady(void) const {return ready;}
	public:operator bool(void) const {return isReady();}
	public:operator const char*(void) const {return ready? buffer: nullptr;}
};
};
#endif
